/* GLUCo - OpenGL Unified Code
 * 
 * Copyright (c) 2007 Pedro Leite.
 * 
 * Written by Pedro Leite, <pedro.leite@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "window_manager.h"

WindowManager* manager = 0;

#define CHECK_MANAGER() {\
	if (!manager) {\
		REPORT_ERROR("Window manager not initialized. Exiting.")\
		exit(-1);\
	}\
}\

static void window_manager_display_proxy_func(void) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win)
		win->display_func();
}

static void window_manager_reshape_proxy_func(int width, int height) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->reshape_func)
		win->reshape_func(width, height);
}

static void window_manager_keyboard_proxy_func(unsigned char key_value, int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->keyboard_func)
		win->keyboard_func(key_value, x, y);
}

static void window_manager_keyboard_up_proxy_func(unsigned char key_value, int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->keyboard_up_func)
		win->keyboard_up_func(key_value, x, y);
}

static void window_manager_special_proxy_func(int key_value, int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win->special_func)
		win->special_func(key_value, x, y);
}

static void window_manager_special_up_proxy_func(int key_value, int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->special_up_func)
		win->special_up_func(key_value, x, y);
}

static void window_manager_mouse_proxy_func(int button, int state, int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->mouse_func)
		win->mouse_func(button, state, x, y);
}

static void window_manager_motion_proxy_func(int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->motion_func)
		win->motion_func(x, y);
}

static void window_manager_passive_motion_proxy_func(int x, int y) {
	int id = glutGetWindow();
	Window* win = g_hash_table_lookup(manager->windows, &id);

	if (win && win->passive_motion_func)
		win->passive_motion_func(x, y);
}

static void idle_windows(gpointer key, gpointer value, gpointer user_data) {
	Window* win = ((Window*) value);
	if (win->idle_func) {
		glutSetWindow(win->id);
		win->idle_func();
	}
}

static void window_manager_idle_proxy_func(void) {
	int id = 0;

	id = glutGetWindow();
	g_hash_table_foreach(manager->windows, idle_windows, 0);
	if (g_hash_table_lookup(manager->windows, &id))
		glutSetWindow(id);
}

void init_window_manager(int argc, char* argv[], unsigned int displayMode) {
	if (!manager) {
		manager = (WindowManager*) malloc(sizeof(WindowManager));
		manager->windows = g_hash_table_new(g_int_hash, g_int_equal);
		manager->idle = false;
		manager->in_main_loop = false;

		glutInit(&argc, argv);
		glutInitDisplayMode(displayMode);
	}
}

void destroy_window_manager(void) {
	if (manager) {
		g_hash_table_destroy(manager-> windows);
		free(manager);
		manager = 0;
	}
}

static void set_window_manager_window_functions(Window* window) {
	glutSetWindow(window->id);

	if (window->init_func)
		window->init_func();
	glutDisplayFunc(window_manager_display_proxy_func);
	glutReshapeFunc(window_manager_reshape_proxy_func);
	glutKeyboardFunc(window_manager_keyboard_proxy_func);
	glutKeyboardUpFunc(window_manager_keyboard_up_proxy_func);
	glutSpecialFunc(window_manager_special_proxy_func);
	glutSpecialUpFunc(window_manager_special_up_proxy_func);
	glutMouseFunc(window_manager_mouse_proxy_func);
	glutMotionFunc(window_manager_motion_proxy_func);
	glutPassiveMotionFunc(window_manager_passive_motion_proxy_func);
	glutReshapeWindow(window->width, window->height);
	glutPositionWindow(window->x_pos, window->y_pos);
}

void window_manager_register_window(Window* window) {
	Window* win = 0;
	CHECK_MANAGER()

	if (!window) {
		REPORT_ERROR("Cannot register a null window.")
		return;
	}

	if (!window->display_func) {
		REPORT_ERROR("Cannot register a window that have a display_func null.")
		return;
	}

	win = g_hash_table_lookup(manager->windows, &window->id);
	if (win) {
		REPORT_ERROR("Window already registered.")
		return;
	}

	window->id = glutCreateWindow(window->title);
	if (manager->in_main_loop)
		set_window_manager_window_functions(window);

	g_hash_table_insert(manager->windows, &window->id, window);
}

void window_manager_unregister_window(Window* window) {
	Window* win = 0;
	CHECK_MANAGER()

	if (!window)
		REPORT_ERROR("Cannot unregister a null window.")

	win = g_hash_table_lookup(manager->windows, &window->id);
	if (!win) {
		REPORT_ERROR("Window not registered.")
		return;
	}

	g_hash_table_remove(manager->windows, &window->id);
	
	glutDestroyWindow(window->id);
}

Window* get_window_manager_current_window(void) {
	int id = 0;
	CHECK_MANAGER()

	id = glutGetWindow();
	return g_hash_table_lookup(manager->windows, &id);
}

void window_manager_enable_idle(bool flag) {
	CHECK_MANAGER()

	if (flag)
		glutIdleFunc(window_manager_idle_proxy_func);
	else
		glutIdleFunc(0);

	manager->idle = flag;
}

static void init_windows(gpointer key, gpointer value, gpointer user_data) {
	set_window_manager_window_functions((Window*) value);
}

void window_manager_start_main_loop(void) {
	CHECK_MANAGER()

	g_hash_table_foreach(manager->windows, init_windows, 0);

	manager->in_main_loop = true;

	glutMainLoop();
}
